#include "DatalogProgram.h"
#include "Token.h"
#include "Parameter.h"
#include "Predicate.h"
#include "Rule.h"
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

string numToString(int num);

DatalogProgram::DatalogProgram(char* input) {
	scan = new Scanner(input);
	nextToken = new Token();
	currentPredicate = new Predicate(*nextToken);
	currentRule = new Rule(*currentPredicate);
	*nextToken = scan->getFirstToken();
	fail = false;
	if (!scan->successfulCompile())
	{
		fail = true;
	}
}

bool DatalogProgram::isDatalogProgram() {
	int wcv = 0;
	while (!fail && wcv < 13) {
		switch (wcv) {
			case 0: matchTokenType("SCHEMES");
				break;
			case 1: matchTokenType("COLON");
				break;
			case 2: fail = !isSchemeList();
				break;
			case 3: matchTokenType("FACTS");
				break;
			case 4: matchTokenType("COLON");
			break;
			case 5: fail = !isFactList();
				break;
			case 6: matchTokenType("RULES");
			break;
			case 7: matchTokenType("COLON");
			break;
			case 8: fail = !isRuleList();
				break;
			case 9: matchTokenType("QUERIES");
			break;
			case 10: matchTokenType("COLON");
			break;
			case 11: fail = !isQueryList();
				break;
			case 12: matchTokenType("EOF"); 
				break;
			default: cout << "Oops, too far " << wcv << endl;
				break;
		}
		wcv++;
	}
	return !fail;

}

string DatalogProgram::toString() {
	string temp = "";
	if (!scan->successfulCompile())
	{
		return scan->toString();
	}
	if (fail) {
		temp += "Failure!\n\t" + nextToken->toString();
	}
	else {
		temp += "Success!" + schemesString() + factsString() + rulesString() + queriesString() + domainString();
	}
	return temp;
}

string DatalogProgram::schemesString() {
	string temp = "\nSchemes(" + numToString(schemes.size()) + "):\n";
	for (int lcv = 0; lcv < schemes.size(); lcv++) {
		temp += "\t" + schemes[lcv].toString() + "\n";
	}

	return temp;
}

string DatalogProgram::factsString() {
	string temp = "Facts(" + numToString(facts.size()) + "):\n";
	for (int lcv = 0; lcv < facts.size(); lcv++) {
		temp += "\t" + facts[lcv].toString() + ".\n";
	}

	return temp;
}

string DatalogProgram::rulesString() {
	string temp = "Rules(" + numToString(rules.size()) + "):\n";
	for (int lcv = 0; lcv < rules.size(); lcv++) {
		temp += "\t" + rules[lcv].toString() + "\n";
	}

	return temp; "";
}

string DatalogProgram::queriesString() {
	string temp = "Queries(" + numToString(queries.size()) + "):\n";
	for (int lcv = 0; lcv < queries.size(); lcv++) {
		temp += "\t" + queries[lcv].toString() + "?\n";
	}

	return temp;
}

string DatalogProgram::domainString() {
	string temp = "Domain(" + numToString(domain.size()) + "):\n";
	map <string, Token>::iterator it = domain.begin();
	for (it; it!=domain.end(); ++it)
	{
		temp += "\t'" + it->first + "'\n";
	}

	return temp;
}

bool DatalogProgram::matchTokenType(string type) {
	if (nextToken->getType() != type) {
		fail = true;
	}
	else {
		scan->removeFirstToken();
		*nextToken = scan->getFirstToken();
	}
	return !fail;
}

bool DatalogProgram::matchTokenType(string type1, string type2) {
	if (nextToken->getType() != type1 && nextToken->getType() != type2) {
		fail = true;
	}
	return !fail;
}
bool DatalogProgram::peekTokenType(string type) {
	bool isType = false;
	if (nextToken->getType() == type) {
		isType = true;
	}
	return isType;
}
bool DatalogProgram::isSchemeList() {
	bool pass = isScheme();
	if (pass)
	{
		isSchemeList();
	}
	return pass;
}

bool DatalogProgram::isScheme() {
	bool isPred = isPredicate(0);
	return isPred;
}

bool DatalogProgram::isFactList() {
	if (nextToken->getType() == "ID")
	{
		int wcv = 0;
		while (!fail && wcv < 2)
		{
			switch(wcv) {
				case 0: fail = !isFact();
					break;
				case 1: fail = !isFactList();
					break;
				default: cout << "Oops 4 far" << endl;
			}
			wcv++;
		}
	}
	return !fail;
}

bool DatalogProgram::isFact() {
	int wcv = 0;
	while (!fail && wcv < 2)
	{
		switch(wcv) {
			case 0: fail = !isPredicate(1);
				break;
			case 1: matchTokenType("PERIOD");
				break;
			default: cout << "Oops 9 far" << endl; 
				break;
		}
		wcv++;
	}
	return !fail;
}

bool DatalogProgram::isRuleList() {
	if (nextToken->getType() == "ID")
	{
		int wcv = 0;
		while (!fail && wcv < 2)
		{
			switch(wcv) {
				case 0: fail = !isRule();
					break;
				case 1: fail = !isRuleList();
					break;
				default: cout << "Oops 5 far" << endl;
			}
			wcv++;
		}
	}
	return !fail;
}

bool DatalogProgram::isRule() {
	int wcv = 0;
	while (!fail && wcv < 4)
	{
		switch(wcv) {
			case 0: fail = !isPredicate(2);
				break;
			case 1: matchTokenType("COLON_DASH");
				break;
			case 2: fail = !isPredicateList(3);
				break;
			case 3: matchTokenType("PERIOD");
				break;
			default: cout << "Oops 6 far" << endl; 
				break;
		}
		wcv++;
	}
	if (!fail) {
		rules.push_back(*currentRule);
	}
	return !fail;
}

void DatalogProgram::makeRule() {
	Rule temp_rule(*currentPredicate);
	*currentRule = temp_rule; 

}

bool DatalogProgram::isQueryList() {
	bool pass = isQuery();
	if (pass)
	{
		if(peekTokenType("ID")) {
			pass = isQueryList();
		}
	}
	return pass;
}

bool DatalogProgram::isQuery() {
	int wcv = 0;
	while (!fail && wcv < 2)
	{
		switch(wcv) {
			case 0: fail = !isPredicate(4);
				break;
			case 1: matchTokenType("Q_MARK");
				break;
			default: cout << "Oops 8 far" << endl; 
				break;
		}
		wcv++;
	}
	return !fail;
}

bool DatalogProgram::isPredicateList(int which_list) {	
	int wcv = 0;
	while (!fail && wcv < 3) {
		switch(wcv) {
			case 0: fail = !isPredicate(which_list);
				break;
			case 1: if (peekTokenType("COMMA")){		
						scan->removeFirstToken();
						*nextToken = scan->getFirstToken();
					}
					else {
						wcv = 3;
					}
				break;
			case 2: fail = !isPredicateList(which_list);
				break;
			default: cout << "Oops 7 far" << endl;
				break;
		}
		wcv++;
	}

	return !fail;
}

bool DatalogProgram::isPredicate(int which_list) {
	int wcv = 0;
	if (nextToken->getType() == "ID")
	{
		Predicate temp_pred(*nextToken);
		*currentPredicate = temp_pred;
	}
	while(!fail && wcv < 4) {
		switch(wcv) {
		case 0: matchTokenType("ID");
			break;
		case 1: matchTokenType("LEFT_PAREN");
			break;
		case 2: fail = !isParameterList();
			break;
		case 3: matchTokenType("RIGHT_PAREN");
			break;
		default: cout << "Oops 2 far" << endl;
			break;
		}
		wcv++;
	}
	if (!fail)
	{
		switch(which_list) {
			case 0: schemes.push_back(*currentPredicate);
				break;//schemes
			case 1: facts.push_back(*currentPredicate);
				break;//facts
			case 2: makeRule();
				break;//rules left arg
			case 3: currentRule->pushPredicate(*currentPredicate);
				break;//rules right arg
			case 4: queries.push_back(*currentPredicate);
				break;//queries
			default: cout << "Oops 10 far" << endl; 
				break;
		}
	}
	return !fail;
}

bool DatalogProgram::isParameterList() {
	int wcv = 0;
	while (!fail && wcv < 3) {
		switch(wcv) {
			case 0: fail = !isParameter();
				break;
			case 1: if (peekTokenType("COMMA")){		
						scan->removeFirstToken();
						*nextToken = scan->getFirstToken();
					}
					else {
						wcv = 3;
					}
				break;
			case 2: fail = !isParameterList();
				break;
			default: cout << "Oops 3 far" << endl;
				break;
		}
		wcv++;
	}
	return !fail;
}

bool DatalogProgram::isParameter() {
	if(matchTokenType("ID", "STRING")) {
		makeParameter();
	}
	return !fail;
}


void DatalogProgram::makeParameter() {
	if (nextToken->getType() == "STRING")
	{
		domain[nextToken->getValue()] = *nextToken;
	}
	Parameter temp_param(*nextToken);
	currentPredicate->pushParameter(temp_param);
	scan->removeFirstToken();
	*nextToken = scan->getFirstToken();
}

string numToString(int num) {
	stringstream buffer;
	buffer << num;
	string temp = "";
	buffer >> temp;
	return temp;
}
